package xstring

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/des"
	"errors"
)

// des的cbc模式加密函数
//
//	src	明文
//	key	密钥，大小为8byte
func Des(src, key []byte) ([]byte, error) {
	if len(key) != 8 {
		return nil, errors.New("DES加密仅支持8位长度的密钥")
	}
	//传递两个参数,src为需要加密的明文，返回[]byte类型数据
	//1：创建并返回一个DES算法的cipher.block接口
	block, err := des.NewCipher(key)
	//2：判断是否创建成功
	if err != nil {
		return nil, err
	}
	//3：对最后一个明文分组进行数据填充
	src = pkcs5Padding(src, block.BlockSize())
	//4：创建一个密码分组为链接模式的，底层使用DES加密的BLockMode接口
	//    参数iv的长度，必须等于b的块尺寸
	var tmp []byte
	if len(key) >= 8 {
		tmp = key[0:8]
	} else {
		tmp = key
		for i := len(key); i <= 8; i++ {
			tmp = append(tmp, '0')
		}
	}
	blockmode := cipher.NewCBCEncrypter(block, tmp)
	//5:加密连续的数据块
	dst := make([]byte, len(src))
	blockmode.CryptBlocks(dst, src)
	//fmt.Println("加密之后的数据:",dst)
	//6:将加密后的数据返回
	return dst, nil
}

// des解密函数
//
//	src	要解密的密文
//	key	密钥，和加密密钥相同，大小为8byte
func UnDes(src, key []byte) ([]byte, error) {
	if len(key) != 8 {
		return nil, errors.New("DES加密仅支持8位长度的密钥")
	}
	//1:创建并返回一个使用DES算法的cipher.block接口
	block, err := des.NewCipher(key)
	//2:判断是否创建成功
	if err != nil {
		return nil, err
	}
	//创建一个密码分组为链接模式的，底层使用DES解密的BlockMode接口
	var tmp []byte
	if len(key) >= 8 {
		tmp = key[0:8]
	} else {
		tmp = key
		for i := len(key); i <= 8; i++ {
			tmp = append(tmp, '0')
		}
	}
	blockMode := cipher.NewCBCDecrypter(block, tmp)
	//解密数据
	dst := make([]byte, len(src))
	blockMode.CryptBlocks(dst, src)
	//5:去掉最后一组填充数据
	return pkcs5UnPadding(dst)
}

// 删除pkcs5填充的尾部数据
func pkcs5UnPadding(origData []byte) ([]byte, error) {
	//1:计算数据的总长度
	length := len(origData)
	if length <= 0 {
		return nil, errors.New("字节长度不正确")
	}
	//2:根据填充的字节值得到填充的次数
	number := int(origData[length-1])
	if number > length {
		return nil, errors.New("计算结果异常，可能为密钥错误，请检查密钥是否正确")
	}
	//3:将尾部填充的number个字节去掉
	return origData[:(length - number)], nil
}

// 使用pkcs5的方式填充
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
	//1:计算最后一个分组缺多少字节
	padding := blockSize - (len(ciphertext) % blockSize)
	//2:创建一个大小为padding的切片,每个字节的值为padding
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	//3:将padText添加到原始数据的后边，将最后一个分组缺少的字节数补齐
	newText := append(ciphertext, padText...)
	return newText
}

// AES加密
//
//	src	明文
//	key	密钥
func Aes(src, key []byte) ([]byte, error) {
	switch len(key) {
	case 16, 24, 32:
		break
	default:
		return nil, errors.New("AES密钥长度仅允许16、24、32字节长度")
	}
	//传递两个参数,src为需要加密的明文，返回[]byte类型数据
	//1：创建并返回一个DES算法的cipher.block接口
	block, err := aes.NewCipher(key)
	//2：判断是否创建成功
	if err != nil {
		return nil, err
	}
	//3：对最后一个明文分组进行数据填充
	src = pkcs5Padding(src, block.BlockSize())
	//5:加密连续的数据块
	dst := make([]byte, len(src))
	blockmode := cipher.NewCBCEncrypter(block, key[:block.BlockSize()])
	blockmode.CryptBlocks(dst, src)
	//fmt.Println("加密之后的数据:",dst)
	//6:将加密后的数据返回
	return dst, nil
}

// AES解密
//
//	src	加密后的字符串
//	key	密钥标识
func UnAes(src, key []byte) ([]byte, error) {
	switch len(key) {
	case 16, 24, 32:
		break
	default:
		return nil, errors.New("AES密钥长度仅允许16、24、32字节长度")
	}
	//1:创建并返回一个使用DES算法的cipher.block接口
	block, err := aes.NewCipher(key)
	//2:判断是否创建成功
	if err != nil {
		return nil, err
	}
	plaintext := make([]byte, len(src))
	blockMode := cipher.NewCBCDecrypter(block, key[:block.BlockSize()])
	blockMode.CryptBlocks(plaintext, src)
	return pkcs5UnPadding(plaintext)
}
